import java.util.*;
import java.io.*;
import java_cup.runtime.*;  // defines Symbol

/**
 * This program is to be used to test the Mini scanner.
 * This version is set up to test all tokens, identify bad strings, verify
 * character counting is correct, and a lack of EOF is appropriately recognized
 *
 * @author Sean Bauer
 */
public class P2 {
    public static void main(String[] args) throws IOException {
                                           // exception may be thrown by yylex
        
        System.out.println("\n-----\nTest: all tokens...\n-----");
        testAllTokens();
        System.out.println("\ndone.");
        CharNum.num = 1;
        
        System.out.println("\n-----\nTest: bad strings...\n-----");
        testBadStrings();
        System.out.println("\ndone.");
        CharNum.num = 1;
        
        System.out.println("\n-----\nTest: character counting...\n-----");
        testCharCounting();
        System.out.println("\ndone.");
        CharNum.num = 1;
    
        System.out.println("\n-----\nTest: EOF...\n-----");
        testEOF();
        System.out.println("\ndone.");
    }

    /**
     * testAllTokens
     *
     * Open and read from file allTokens.txt
     * For each token read, write the corresponding string to allTokens.out
     * If the input file contains all tokens, one per line, we can verify
     * correctness of the scanner by comparing the input and output files
     * (e.g., using a 'diff' command).
     */
    private static void testAllTokens() throws IOException {
        // open input and output files
        FileReader inFile = null;
        PrintWriter outFile = null;
        try {
            inFile = new FileReader("allTokens.txt");
            outFile = new PrintWriter(new FileWriter("allTokens.out"));
        } catch (FileNotFoundException ex) {
            System.err.println("File allTokens.txt not found.");
            System.exit(-1);
        } catch (IOException ex) {
            System.err.println("allTokens.out cannot be opened.");
            System.exit(-1);
        }

        // create and call the scanner
        Yylex scanner = new Yylex(inFile);
        Symbol token = scanner.next_token();
        while (token.sym != sym.EOF) {
          // Moved all case logic to SymbolToString()
          outFile.println(SymbolToString(token));
          token = scanner.next_token();
        } // end while
        outFile.close();
    }
    
    /**
     * testBadStrings
     *
     * Open and read from file badStrings.txt
     * Is intended to catch all possible bad strings in file and output
     * proper error messages for each.
     */
    private static void testBadStrings() throws IOException {
        // open input and output files
        FileReader inFile = null;
        try {
            inFile = new FileReader("badStrings.txt");
        } catch (FileNotFoundException ex) {
            System.err.println("File badStrings.txt not found.");
            System.exit(-1);
        }
        
        System.out.println("This test should generate 8 errors.\n"); 
        // create and call the scanner
        Yylex scanner = new Yylex(inFile);
        Symbol token = scanner.next_token();
        while (token.sym != sym.EOF) {
            switch (token.sym) {
            case sym.STRINGLITERAL: 
                // Nothing should actually be printed
                System.err.println("A bad string was accepted:" + ((StrLitTokenVal)token.value).strVal);
                break;    
            } // end switch

            token = scanner.next_token();
        } // end while
    }

    /**
     * testCharCounting
     *
     * Reads a single line of input from a file with valid tokens,
     * and compares generated charnum's with actual charnums using
     * indexOf().
     *
     * Tests with non-valid strings charnum correctness were done
     * by manual inspection - since the Scanner does not generate
     * tokens for bad strings, I couldn't find an elegant way to 
     * automate the process.
     *
     */
    private static void testCharCounting() throws IOException {
        // open input and output files
        FileReader inFile1 = null;
        FileReader inFile2 = null;
        try {
            inFile1 = new FileReader("validCharCounting.txt");
            inFile2 = new FileReader("validCharCounting.txt");
        } catch (FileNotFoundException ex) {
            System.err.println("File charCounting.txt not found.");
            System.exit(-1);
        }
        
        java.util.Scanner reference = new java.util.Scanner(inFile1);
        String ref_string = reference.nextLine();
        int chars_processed = 0;

        // create and call the scanner
        Yylex scanner = new Yylex(inFile2);
        Symbol token = scanner.next_token();
        while (token.sym != sym.EOF) {
            int expected_charnum = ((TokenVal)token.value).charnum;
            // Add 1 here since charnums start from 1 in our scanner
            int found_charnum = ref_string.indexOf(SymbolToString(token), chars_processed) + 1;
            if (expected_charnum != found_charnum)
            {
              System.err.println("Bad charnum detected with: " + SymbolToString(token) + " stored=" + expected_charnum + ", actual=" + found_charnum);
            }
            chars_processed += SymbolToString(token).length();  // Ensure we search after the current symbol on next check
            token = scanner.next_token();
        } // end while
    }
    
    /**
     * testEOF
     *
     * Reads file without an EOF token to check that scanner correctly detects
     * this as a unterminated string.
     */
    private static void testEOF() throws IOException {
        // open input and output files
        FileReader inFile = null;
        try {
            inFile = new FileReader("eof.txt");
        } catch (FileNotFoundException ex) {
            System.err.println("File eof.txt not found.");
            System.exit(-1);
        }

        System.out.println("This test should generate a single error.\n"); 
        // create and call the scanner
        Yylex scanner = new Yylex(inFile);
        Symbol token = scanner.next_token();
        while (token.sym != sym.EOF) {
            token = scanner.next_token();
        } // end while
    }


    // Refactoring of existing case statement to simplify earlier code
    private static String SymbolToString(Symbol token) {
      switch (token.sym) {
      case sym.INT:
          return "int";
      case sym.BOOL:
          return "bool"; 
      case sym.VOID:
          return "void";
      case sym.TRUE:
          return "true"; 
      case sym.FALSE:
          return "false"; 
      case sym.IF:
          return "if";
      case sym.ELSE:
          return "else";
      case sym.WHILE:
          return "while";
      case sym.RETURN:
          return "return";
      case sym.CIN:
          return "cin"; 
      case sym.COUT:
          return "cout";
      case sym.ID:
          return ((IdTokenVal)token.value).idVal;
      case sym.INTLITERAL:  
          return String.valueOf(((IntLitTokenVal)token.value).intVal);
      case sym.STRINGLITERAL: 
          return ((StrLitTokenVal)token.value).strVal;
      case sym.LCURLY:
          return "{";
      case sym.RCURLY:
          return "}";
      case sym.LPAREN:
          return "(";
      case sym.RPAREN:
          return ")";
      case sym.LSQBRACKET:
          return "[";
      case sym.RSQBRACKET:
          return "]";
      case sym.COMMA:
          return ",";
      case sym.ASSIGN:
          return "=";
      case sym.SEMICOLON:
          return ";";
      case sym.PLUS:
          return "+";
      case sym.MINUS:
          return "-";
      case sym.TIMES:
          return "*";
      case sym.DIVIDE:
          return "/";
      case sym.NOT:
          return "!";
      case sym.AND:
          return "&&";
      case sym.OR:
          return "||";
      case sym.EQUALS:
          return "==";
      case sym.NOTEQUALS:
          return "!=";
      case sym.LESS:
          return "<";
      case sym.GREATER:
          return ">";
      case sym.LESSEQ:
          return "<=";
      case sym.GREATEREQ:
          return ">=";
      case sym.WRITE:
          return "<<";
      case sym.READ:
          return ">>";
      default:
          return null;
      } // end switch
    }
}
